package ldh.text.controller;

import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Point2D;
import javafx.scene.control.IndexRange;
import javafx.scene.control.TextField;
import javafx.scene.input.*;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import ldh.text.controller.segment.TextStatusPaneController;
import ldh.text.service.FileDataService;
import ldh.text.util.RegionUtil;
import ldh.text.util.ThreadPoolUtil;
import org.fxmisc.flowless.VirtualizedScrollPane;
import org.fxmisc.richtext.LineNumberFactory;
import org.fxmisc.richtext.Selection;
import org.fxmisc.richtext.SelectionImpl;
import org.fxmisc.richtext.StyleClassedTextArea;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class SmallDataTextAreaController implements Initializable {

    private static final Logger LOGGER = LoggerFactory.getLogger(SmallDataTextAreaController.class);

    private VirtualizedScrollPane virtualizedScrollPane;
    private StyleClassedTextArea textArea;

    @FXML VBox root;

    // 查询
    @FXML private Pane searchPane;
    @FXML private TextField searchTextField;
    @FXML private TextStatusPaneController textStatusPaneController;

    private FileDataService dataService;

    private FileChooser fileChooser = new FileChooser();

    private File file = null;
    private int currentSelect = 0;
    private List<int[]> searchRangeList = new LinkedList();

    private ClickPoint clickPoint = null;
    private List<Selection> selectionList = new ArrayList();
    private boolean isCtrlDown = false;
    private AtomicInteger testInt = new AtomicInteger(0);

    public void setDataService(FileDataService cacheDataService) {
        this.dataService = cacheDataService;
        this.file = cacheDataService.getFile();
    }

    public void loadData(boolean isWrapText) {
        textArea.setWrapText(isWrapText);
        ThreadPoolUtil.submit(()->{
            try {
                ByteBuffer data = dataService.loadData(0, (int)dataService.dataSize());
                String content = new String(data.array(), 0, data.limit(), "utf8");
                Platform.runLater(()->{
                    textArea.insertText(0, content);
                    textArea.requestFocus();
                });
            } catch (Exception e) {
                LOGGER.error("open file error", e);
            }
        });
    }

    public File saveData() {
        OpenOption openOption = null;
        if (file == null) {
            File selectFile = fileChooser.showSaveDialog(textArea.getScene().getWindow());
            if (selectFile == null) return null;
            if (selectFile.isDirectory()) {
                String fileName = "mini-" + System.currentTimeMillis() +".txt";
                file = new File(selectFile, fileName);
            } else {
                file = selectFile;
                if (file.exists()) {
                    openOption = StandardOpenOption.WRITE;
                }
            }
        } else {
            openOption = StandardOpenOption.WRITE;
        }

        ThreadPoolUtil.submit(()->{
            try {
                String text = getText();
                byte[] data = text.getBytes("utf8");
                dataService.saveData(file, data);
            } catch (Exception e) {
                LOGGER.error("save data error", e);
            }
        });
        return file;
    }

    public void setWrapText(boolean isWrap) {
        int currentParagraph = textArea.getCurrentParagraph();
        IndexRange indexRange = textArea.getSelection();

        textArea.setWrapText(isWrap);
        if (isWrap) {
            String text = textArea.getContent().getText();
            textArea.clear();
            textArea.insertText(0, text);
            textArea.showParagraphAtTop(currentParagraph);
            textArea.selectRange(indexRange.getStart(), indexRange.getEnd());
        }
    }

    public String getText() {
        return textArea.getText();
    }

    public void moveTo(int start, int end) {
        textArea.moveTo(start);
        LOGGER.info("position: {}", textArea.getCaretPosition());
        textArea.selectRange(start, end);
        textArea.requestFollowCaret();

        double x=0, y=0;
        if (textArea.getSelectionBounds().isPresent()) {
            x = textArea.getSelectionBounds().get().getMinX();
            y = textArea.getSelectionBounds().get().getMaxY();
        } else if (textArea.getCaretBounds().isPresent()) {
            x = textArea.getCaretBounds().get().getMinX();
            y = textArea.getCaretBounds().get().getMinY();
        }

        if (x>0 || y > 0) {
            Point2D point2D = textArea.localToScene(0, 0);
            double screenX = x + point2D.getX();
            double screenY = y +point2D.getY();
            textArea.fireEvent(new MouseEvent(MouseEvent.MOUSE_CLICKED, 1, 1, screenX, screenY, MouseButton.PRIMARY, 1,
                    true, true, true, true, true, true, true, true, true, true, null));

        }
    }

    public void focus(){
        textArea.requestFocus();
    }

    @FXML
    public void closeSearchPane(ActionEvent actionEvent) {
        searchPane.setVisible(false);
        searchPane.setManaged(false);
    }

    @FXML
    public void search(ActionEvent actionEvent) {
        String searchText = searchTextField.getText().trim();
        if (searchText.equals("")) return;
        findText(searchText);
    }

    @FXML
    public void moveToNext(ActionEvent actionEvent) {
        if (searchRangeList.size() < 1) return;
        if (searchRangeList.size() - 1 == currentSelect) {
            currentSelect = 0;
        }
        moveTo(searchRangeList.get(currentSelect)[0], searchRangeList.get(currentSelect)[1]);
        currentSelect++;
    }

    @FXML
    public void moveToPre(ActionEvent actionEvent) {
        if (searchRangeList.size() < 1) return;
        if (currentSelect == 0) {
            currentSelect = searchRangeList.size() - 1;
        }
        moveTo(searchRangeList.get(currentSelect)[0], searchRangeList.get(currentSelect)[1]);
        currentSelect--;
    }

    private void findText(String searchText) {
        if (textArea == null) return;
        searchRangeList.forEach(array->{
            textArea.clearStyle(array[0], array[1]);
        });
        searchRangeList.clear();
        ThreadPoolUtil.submit(()->{
            Pattern pattern = Pattern.compile(searchText);
            Matcher matcher = pattern.matcher(textArea.getText());
            int t = matcher.groupCount();
            LOGGER.info("search total:" + t);
            while(matcher.find()) {
                int start = matcher.start();
                int end = matcher.end();
                searchRangeList.add(new int[]{start, end});
            }
            searchRangeList.forEach(array->{
                Platform.runLater(()->{
                    textArea.setStyleClass(array[0], array[1], "select-text");
                });
            });
        });
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        textArea = new StyleClassedTextArea();
        textArea.setParagraphGraphicFactory(LineNumberFactory.get(textArea));
        textArea.getStyleClass().add("text-area");
        virtualizedScrollPane = new VirtualizedScrollPane(textArea);
        root.getChildren().add(1, virtualizedScrollPane);
        VBox.setVgrow(virtualizedScrollPane, Priority.ALWAYS);

//        KeyCombination ctrl_f = new KeyCodeCombination(KeyCode.F, KeyCombination.CONTROL_DOWN);
        textArea.setOnKeyPressed(e->{
            if (e.isControlDown() && e.getCode() == KeyCode.F) {// 查找
                searchPane.setVisible(true);
                searchPane.setManaged(true);
            } else if (e.isControlDown() && e.getCode() == KeyCode.S) {// 保存文件
                saveData();
            }

            if (e.getCode() == KeyCode.CONTROL) {
                isCtrlDown = true;
            }

            if (e.getCode() == KeyCode.ESCAPE) {
                textArea.deselect();
                for(Selection selection : selectionList) {
                    textArea.removeSelection(selection);
                }
                selectionList.clear();
            }
            LOGGER.info("control: {}", isCtrlDown);
        });
        textArea.setOnKeyReleased(e->{
            if (e.isControlDown()) {
                isCtrlDown = true;
            } else {
                isCtrlDown = false;
                LOGGER.info("key release");
            }
        });

        textArea.setOnMouseClicked(e->{
            if(!isCtrlDown) {
                clickPoint = null;
                return;
            }
            for(Selection selection : selectionList) {
//                textArea.clearStyle(selection.getStartParagraphIndex(), selection.getStartPosition(), selection.getEndPosition());
                boolean isSuccess = textArea.removeSelection(selection);
                if (isSuccess) {
                    testInt.decrementAndGet();
                }
                if (!isSuccess) {
                    LOGGER.info("remove selection false");
                }
            }
            textArea.deselect();
            textArea.requestLayout();
            selectionList.clear();

            LOGGER.info("clicked: caretPoint:{}, paragraph:{}, column:{}, int {}", textArea.getCaretPosition(),
                    textArea.getCurrentParagraph(), textArea.getCaretColumn(), testInt.get());
            if(clickPoint == null){
                clickPoint = new ClickPoint(textArea.getCurrentParagraph(), textArea.getCaretColumn(), textArea.getCaretPosition());
            } else {
                int currentParagraph = textArea.getCurrentParagraph();
                int caretColumn = textArea.getCaretColumn();

                if (clickPoint.getParagraph() < currentParagraph) {
                    for (int i=clickPoint.getParagraph(); i<=currentParagraph; i++) {
                        int anchor = textArea.getAbsolutePosition(i, clickPoint.getColumn());
                        int caretPosition = textArea.getAbsolutePosition(i, caretColumn);
                        Selection selection = new SelectionImpl<>("s" + i, textArea, anchor, caretPosition);
                        textArea.addSelection(selection);
                        selectionList.add(selection);
                        testInt.incrementAndGet();
                    }
                } else {
                    for (int i=currentParagraph; i<=clickPoint.getParagraph(); i++) {
                        int anchor = textArea.getAbsolutePosition(i, clickPoint.getColumn());
                        int caretPosition = textArea.getAbsolutePosition(i, caretColumn);
                        Selection selection = new SelectionImpl<>("tt" + i, textArea, anchor, caretPosition);
                        textArea.addSelection(selection);
                        selectionList.add(selection);
                        testInt.incrementAndGet();
                    }
                }
            }
        });

        textArea.caretPositionProperty().addListener(l->{
            int position = textArea.getCaretPosition();
            if (position < 1) return;
            for(int i=0, lg=searchRangeList.size()-1; i<lg; i++) {
                if (searchRangeList.get(i)[0] < position && searchRangeList.get(i+1)[0] > position) {
                    currentSelect = i;
                    break;
                }
            }
        });

        LOGGER.info("dd {}", textStatusPaneController);
    }
}
